<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            overflow: hidden;
        }
    </style>
</head>

<body>
    <canvas id="canvas"></canvas>
</body>
<script id="ColorVS" type="x-shader/x-vertex">
    attribute vec4 a_Position;
    uniform float u_PointSize;
    uniform mat4 u_ModelM4;
    uniform mat4 u_PvM4;
    void main(){
        gl_Position = u_PvM4 * u_ModelM4 * a_Position;
        gl_PointSize = u_PointSize;
    }
</script>
<script id="ColorFS" type="x-shader/x-fragment">
    precision mediump float;
    uniform vec4 u_Color;
    void main(){
        gl_FragColor = u_Color;
    }
</script>
<script id="GrassVS" type="x-shader/x-vertex">
    attribute vec4 a_Position;
    uniform float u_PointSize;
    uniform mat4 u_ModelM4;
    uniform mat4 u_PvM4;
    varying vec3 v_Posi;
    void main(){
        gl_Position = u_PvM4 * u_ModelM4 * a_Position;
        gl_PointSize = u_PointSize;
        v_Posi = vec3(a_Position);
    }
</script>
<script id="GrassFS" type="x-shader/x-fragment">
    precision mediump float;
    uniform vec4 u_Color;
    varying vec3 v_Posi;
    void main(){
        //判断里地面的距离
        float y = v_Posi.y;
        float dist = smoothstep(0.0, 0.5, y);
        gl_FragColor = vec4(0.1, 0.6 + dist * 0.4, 0.1, 1.0);
    }
</script>

<script id="ViewVS" type="x-shader/x-vertex">
    attribute vec4 a_Position;
    attribute vec2 a_Pin;
    uniform mat4 u_ModelM4;
    uniform mat4 u_PvM4;
    varying vec2 v_Pin;
    void main(){
        gl_Position = u_PvM4 * u_ModelM4 * a_Position;
        gl_PointSize = 30.0;
        v_Pin = a_Pin;
    }
</script>
<script id="ViewFS" type="x-shader/x-fragment">
    precision mediump float;
    uniform sampler2D u_Sampler;
    varying vec2 v_Pin;
    void main(){
       gl_FragColor = texture2D(u_Sampler, v_Pin);
    }
</script>
<script type="module">
    import { Matrix4, Vector3, Color, OrthographicCamera, PerspectiveCamera, Spherical } from '../js/three.module.js';
    import { CreateProgram, imgPromise } from '../js/Utils.js';
    import lib from '../js/ThreeLib.js';
    import CameraController from '../js/CameraController.js'
    import Poly from '../js/Poly4.js'
    import Control from '../js/Control.js'
    import Earth from '../js/objs/Earth.js'

    import Geo from '../js/Poly2/Geo.js'
    import Mat from '../js/Poly2/Mat.js'
    import Obj3D from '../js/Poly2/Obj3D.js'
    import Scene from '../js/Poly2/Scene.js'
    import Frame from '../js/Poly2/Frame.js'

    //#region gl
    const canvas = document.getElementById("canvas");
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    const gl = canvas.getContext("webgl", { antialias: true, });

    gl.enable(gl.BLEND);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);//去图片描边
    gl.enable(gl.DEPTH_TEST);
    //#endregion

    const frame = new Frame({ gl: gl });
    const scene = new Scene({ gl: gl });
    frame.addAxisProgram();
    frame.regProgram('color', {
        program: CreateProgram(gl, document.querySelector('#ColorVS').innerText, document.querySelector('#ColorFS').innerText),
        attribs: ['a_Position'],
        uniforms: ['u_PvM4', 'u_ModelM4', 'u_PointSize', 'u_Color',]
    });
    frame.regProgram('grass', {
        program: CreateProgram(gl, document.querySelector('#GrassVS').innerText, document.querySelector('#GrassFS').innerText),
        attribs: ['a_Position'],
        uniforms: ['u_PvM4', 'u_ModelM4', 'u_PointSize', 'u_Color']
    });
    scene.regProgram('view', {
        program: CreateProgram(gl, document.querySelector('#ViewVS').innerText, document.querySelector('#ViewFS').innerText),
        attribs: ['a_Position', 'a_Pin'],
        uniforms: ['u_PvM4', 'u_ModelM4', 'u_Sampler']
    });


    const pvM4 = new Matrix4();
    const modelM4 = new Matrix4();
    const eye = new Vector3(2, 1, 2);
    const lookAt = new Vector3();

    const camera = new PerspectiveCamera(45, canvas.width / canvas.height, 1, 20);
    const cameraController = new CameraController({
        canvas,
        camera,
        pvMatrix: pvM4,
        eye,
        lookAt,
    });



    const axis = new Obj3D().createAxis('axis');
    frame.add(axis);




    //#region main
    //屏幕对象

    const view = createView();
    scene.add(view);


    //创建一块地板
    const [floorW, floorH] = [1, 1];
    const floorEach = [10, 10];
    const [floorVertices, floorElements] = [
        [
            -floorW, 0, -floorH,
            -floorW, 0, floorH,
            floorW, 0, floorH,
            floorW, 0, -floorH,
        ], [0, 1, 2, 0, 2, 3]
    ];
    const floor = create(floorVertices, floorElements, [0.5, 0.5, 0.5, 1.0]);
    frame.add(floor);




    const [grassW, grassH, grassRank] = [0.02, 0.5, 5];

    let count = 2000;//生成数量
    const interval = Math.sqrt(count).toFixed(0);//生成数量



    frame.add(grass(count));
    //frame.add(createLine(grass[0], grass[2], [1, 1, 1, 1]));

    //#endregion


    //#region 控制面板
    let control = new Control();
    control.add({
        name: 'Count',
        max: 50000, min: 100, value: count, step: 1,
        action: function (value) {
            count = Number(value);

            frame.clear();
            let curCounr = count;
            if (curCounr >= 5000) {
                const len = Math.floor(count / 5000);
                for (let i = 0; i < len; i++) {
                    if (curCounr - 5000 > 0) {
                        curCounr -= 5000;
                        frame.add(floor, grass(5000));
                    } else {
                        frame.add(floor, grass(curCounr));
                    }
                }
            } else {
                frame.add(floor, grass(curCounr));
            }

        }
    });
    //#endregion

    function grass(count) {
        const posi = getAvgArr(interval);
        const grass = createGrassData(posi);
        return createGrass(grass[0], grass[1], [0, 1, 0.5, 1]);
    }
    function createGrassData(dataArr) {
        const vertices = [];
        const elements = [];
        let curElementIndex = 0;
        //额外输出一组线段
        const elementsOfLine = [];
        //总弯曲角度(6-45)
        const angle6 = Math.PI / 30;
        const angle45 = Math.PI / 4;
        const angle45_6 = angle45 - angle6;


        for (let i = 0; i < dataArr.length; i++) {
            const { x, y, z, n } = dataArr[i];
            const pos = new Vector3(x, y, z);

            const tangent = new Vector3(1, 0, 0);//默认切线
            const l = tangent.clone().cross(n).normalize();
            const r = n.clone().cross(tangent).normalize();
            const t = new Vector3(0, grassH, 0).normalize();

            let tN = t.clone().setLength(grassH);
            //分层
            const curVertices = [tN];//直接插入头部
            const rank = grassRank >= 1 ? grassRank : 1;
            const eachH = grassH / rank;
            const eachW = grassW / rank;
            for (let j = 0; j < rank; j++) {
                //第一段
                const curRank = j + 1;
                const curRankW = curRank * eachW;
                const curRankH = grassH - curRank * eachH;
                if (j == 0) {
                    //第一层只需要一个三角形
                    //  0
                    // 1 2
                    let lNx = l.clone().setLength(curRankW).z;
                    let lNy = Math.abs(l.clone().setLength(curRankH).z);
                    let rNx = r.clone().setLength(curRankW).z;
                    let rNy = Math.abs(r.clone().setLength(curRankH).z);


                    curVertices.push(new Vector3(lNx, lNy, 0), new Vector3(rNx, rNy, 0))
                    elements.push(curElementIndex, curElementIndex + 1, curElementIndex + 2);
                    //额外输出一组线段
                    elementsOfLine.push(curElementIndex, curElementIndex + 1, curElementIndex + 1, curElementIndex + 2, curElementIndex + 2, curElementIndex);

                    curElementIndex += 3;
                } else {
                    //第1+n层需要两个三角形
                    //  1 2
                    // 3   4
                    //2,1,3, 2,3,4

                    let lNx = l.clone().setLength(curRankW).z;
                    let lNy = Math.abs(l.clone().setLength(curRankH).z);
                    let rNx = r.clone().setLength(curRankW).z;
                    let rNy = Math.abs(r.clone().setLength(curRankH).z);

                    curVertices.push(new Vector3(lNx, lNy, 0), new Vector3(rNx, rNy, 0))
                    elements.push(curElementIndex - 1, curElementIndex - 2, curElementIndex, curElementIndex - 1, curElementIndex, curElementIndex + 1);
                    //额外输出一组线段
                    elementsOfLine.push(
                        curElementIndex - 1, curElementIndex - 2, curElementIndex - 2, curElementIndex, curElementIndex, curElementIndex - 1,
                        curElementIndex - 1, curElementIndex, curElementIndex, curElementIndex + 1, curElementIndex + 1, curElementIndex - 1
                    );

                    curElementIndex += 2;
                }
            }


            //随机旋转
            const angle = Math.random() * Math.PI * 2 - Math.PI;
            const rotM4 = new Matrix4().makeRotationY(angle);
            //弯曲
            const curved = (Math.random() * angle45_6 + angle6) / grassRank;//每段弯曲弧度

            for (let j = 0; j < curVertices.length; j++) {
                const curN = curVertices[j];
                //弯曲
                const rank = grassRank - Math.floor((j + 1) / 2);
                const curvedM4 = new Matrix4().makeRotationX(curved * -rank);

                const curN1 = curN.clone()
                    .applyMatrix4(curvedM4)
                    .applyMatrix4(rotM4)
                    .add(pos);
                vertices.push(...curN1);
            }

        }

        return [vertices, elements, elementsOfLine];
    }
    function getAvgArr(count) {
        const arr = [];
        const [eachX, eachY] = [floorW * 2 / count, floorH * 2 / count];
        for (let i = 0; i < count; i++) {
            const x = eachX * i - floorW;
            for (let j = 0; j < count; j++) {
                const y = eachY * j - floorH;
                arr.push({ x: x, y: 0, z: y, n: new Vector3(0, 1, 0) });
            }
        }
        return arr;
    }

    (function ani(time) {

        draw();
        requestAnimationFrame(ani)
    })(0);

    function createView() {
        const w = 1;
        const h = w / camera.aspect;
        const geo = new Geo({
            data: {
                'a_Position': {
                    size: 3, array: new Float32Array([
                        -w, h, 0,
                        -w, -h, 0,
                        w, -h, 0,
                        w, h, 0,
                    ])
                },
                'a_Pin': { size: 2, array: new Float32Array([0, 1, 0, 0, 1, 0, 1, 1]) },
            },
            element: { array: new Uint16Array([0, 1, 2, 0, 2, 3]) }
        });
        const mat = new Mat({
            program: 'view',
            mode: ['TRIANGLES'],
            data: {
                'u_PvM4': { type: 'uniformMatrix4fv', value: new Matrix4().elements },
                'u_ModelM4': { type: 'uniformMatrix4fv', value: new Matrix4().elements },
            },
        });
        return new Obj3D({ geo: geo, mat: mat, });
    }
    function create(vertices, elements = null, color = [Math.random(), Math.random(), Math.random(), 1]) {
        const geo = new Geo({
            data: {
                'a_Position': { size: 3, array: new Float32Array(vertices) },
            }
        });
        const mat = new Mat({
            program: 'color',
            mode: ['LINES', 'POINTS'],
            data: {
                'u_PvM4': { type: 'uniformMatrix4fv', value: pvM4.elements },
                'u_ModelM4': { type: 'uniformMatrix4fv', value: modelM4.elements },
                'u_PointSize': { type: 'uniform1f', value: 30 },
                'u_Color': { type: 'uniform4fv', value: color },
            },
        });
        if (elements != null) {
            geo.element = { array: new Uint16Array(elements) };
            mat.mode = ['TRIANGLES'];
        }
        return new Obj3D({ geo: geo, mat: mat, });
    }

    function createLine(vertices, elements, color = [Math.random(), Math.random(), Math.random(), 1]) {
        const geo = new Geo({
            data: {
                'a_Position': { size: 3, array: new Float32Array(vertices) },
            },
            element: { array: new Uint16Array(elements) }
        });
        const mat = new Mat({
            program: 'color',
            mode: ['LINES'],
            data: {
                'u_PvM4': { type: 'uniformMatrix4fv', value: pvM4.elements },
                'u_ModelM4': { type: 'uniformMatrix4fv', value: modelM4.elements },
                'u_PointSize': { type: 'uniform1f', value: 30 },
                'u_Color': { type: 'uniform4fv', value: color },
            },
        });
        return new Obj3D({ geo: geo, mat: mat, });
    }
    function createGrass(vertices, elements = null, color = [Math.random(), Math.random(), Math.random(), 1]) {
        const geo = new Geo({
            data: {
                'a_Position': { size: 3, array: new Float32Array(vertices) },
            }
        });
        const mat = new Mat({
            program: 'grass',
            mode: ['LINES', 'POINTS'],
            data: {
                'u_PvM4': { type: 'uniformMatrix4fv', value: pvM4.elements },
                'u_ModelM4': { type: 'uniformMatrix4fv', value: modelM4.elements },
                'u_PointSize': { type: 'uniform1f', value: 30 },
                'u_Color': { type: 'uniform4fv', value: color },
            },
        });
        if (elements != null) {
            geo.element = { array: new Uint16Array(elements) };
            mat.mode = ['TRIANGLES'];
        }
        return new Obj3D({ geo: geo, mat: mat, });
    }

    function draw() {
        frame.setUniform('u_PvM4', { value: pvM4.elements });

        const texure = frame.draw();

        view.addTexure(gl, 'u_Sampler', { texture: texure });
        scene.draw();

    }
</script>

</html>